{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 使用重复元素的网络（VGG）\n",
    "\n",
    "AlexNet在LeNet的基础上增加了3个卷积层。但AlexNet作者对它们的卷积窗口、输出通道数和构造顺序均做了大量的调整。虽然AlexNet指明了深度卷积神经网络可以取得出色的结果，但并没有提供简单的规则以指导后来的研究者如何设计新的网络。我们将在本章的后续几节里介绍几种不同的深度网络设计思路。\n",
    "\n",
    "本节介绍VGG，它的名字来源于论文作者所在的实验室Visual Geometry Group [1]。VGG提出了可以通过重复使用简单的基础块来构建深度模型的思路。\n",
    "\n",
    "## VGG块\n",
    "\n",
    "VGG块的组成规律是：连续使用数个相同的填充为1、窗口形状为$3\\times 3$的卷积层后接上一个步幅为2、窗口形状为$2\\times 2$的最大池化层。卷积层保持输入的高和宽不变，而池化层则对其减半。我们使用`vgg_block`函数来实现这个基础的VGG块，它可以指定卷积层的数量`num_convs`和输出通道数`num_channels`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "1"
    }
   },
   "outputs": [],
   "source": [
    "import d2lzh as d2l\n",
    "from mxnet import gluon, init, nd\n",
    "from mxnet.gluon import nn\n",
    "\n",
    "def vgg_block(num_convs, num_channels):\n",
    "    blk = nn.Sequential()\n",
    "    for _ in range(num_convs):\n",
    "        blk.add(nn.Conv2D(num_channels, kernel_size=3,\n",
    "                          padding=1, activation='relu'))\n",
    "    blk.add(nn.MaxPool2D(pool_size=2, strides=2))\n",
    "    return blk"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## VGG网络\n",
    "\n",
    "与AlexNet和LeNet一样，VGG网络由卷积层模块后接全连接层模块构成。卷积层模块串联数个`vgg_block`，其超参数由变量`conv_arch`定义。该变量指定了每个VGG块里卷积层个数和输出通道数。全连接模块则与AlexNet中的一样。\n",
    "\n",
    "现在我们构造一个VGG网络。它有5个卷积块，前2块使用单卷积层，而后3块使用双卷积层。第一块的输出通道是64，之后每次对输出通道数翻倍，直到变为512。因为这个网络使用了8个卷积层和3个全连接层，所以经常被称为VGG-11。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "2"
    }
   },
   "outputs": [],
   "source": [
    "conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面我们实现VGG-11。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "3"
    }
   },
   "outputs": [],
   "source": [
    "def vgg(conv_arch):\n",
    "    net = nn.Sequential()\n",
    "    # 卷积层部分\n",
    "    for (num_convs, num_channels) in conv_arch:\n",
    "        net.add(vgg_block(num_convs, num_channels))\n",
    "    # 全连接层部分\n",
    "    net.add(nn.Dense(4096, activation='relu'), nn.Dropout(0.5),\n",
    "            nn.Dense(4096, activation='relu'), nn.Dropout(0.5),\n",
    "            nn.Dense(10))\n",
    "    return net\n",
    "\n",
    "net = vgg(conv_arch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下面构造一个高和宽均为224的单通道数据样本来观察每一层的输出形状。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "4"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sequential1 output shape:\t (1, 64, 112, 112)\n",
      "sequential2 output shape:\t (1, 128, 56, 56)\n",
      "sequential3 output shape:\t (1, 256, 28, 28)\n",
      "sequential4 output shape:\t (1, 512, 14, 14)\n",
      "sequential5 output shape:\t (1, 512, 7, 7)\n",
      "dense0 output shape:\t (1, 4096)\n",
      "dropout0 output shape:\t (1, 4096)\n",
      "dense1 output shape:\t (1, 4096)\n",
      "dropout1 output shape:\t (1, 4096)\n",
      "dense2 output shape:\t (1, 10)\n"
     ]
    }
   ],
   "source": [
    "net.initialize()\n",
    "X = nd.random.uniform(shape=(1, 1, 224, 224))\n",
    "for blk in net:\n",
    "    X = blk(X)\n",
    "    print(blk.name, 'output shape:\\t', X.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到，每次我们将输入的高和宽减半，直到最终高和宽变成7后传入全连接层。与此同时，输出通道数每次翻倍，直到变成512。因为每个卷积层的窗口大小一样，所以每层的模型参数尺寸和计算复杂度与输入高、输入宽、输入通道数和输出通道数的乘积成正比。VGG这种高和宽减半以及通道翻倍的设计使多数卷积层都有相同的模型参数尺寸和计算复杂度。\n",
    "\n",
    "## 训练模型\n",
    "\n",
    "因为VGG-11计算上比AlexNet更加复杂，出于测试的目的我们构造一个通道数更小，或者说更窄的网络在Fashion-MNIST数据集上进行训练。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "attributes": {
     "classes": [],
     "id": "",
     "n": "5"
    }
   },
   "outputs": [],
   "source": [
    "ratio = 4\n",
    "small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]\n",
    "net = vgg(small_conv_arch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "除了使用了稍大些的学习率，模型训练过程与上一节的AlexNet中的类似。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "training on gpu(0)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 1, loss 0.9308, train acc 0.664, test acc 0.845, time 37.7 sec\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 2, loss 0.4120, train acc 0.851, test acc 0.881, time 36.1 sec\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 3, loss 0.3380, train acc 0.878, test acc 0.898, time 36.2 sec\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 4, loss 0.2961, train acc 0.893, test acc 0.905, time 36.1 sec\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 5, loss 0.2646, train acc 0.903, test acc 0.912, time 36.1 sec\n"
     ]
    }
   ],
   "source": [
    "lr, num_epochs, batch_size, ctx = 0.05, 5, 128, d2l.try_gpu()\n",
    "net.initialize(ctx=ctx, init=init.Xavier())\n",
    "trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr})\n",
    "train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)\n",
    "d2l.train_ch5(net, train_iter, test_iter, batch_size, trainer, ctx,\n",
    "              num_epochs)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 小结\n",
    "\n",
    "* VGG-11通过5个可以重复使用的卷积块来构造网络。根据每块里卷积层个数和输出通道数的不同可以定义出不同的VGG模型。\n",
    "\n",
    "## 练习\n",
    "\n",
    "* 与AlexNet相比，VGG通常计算慢很多，也需要更多的内存或显存。试分析原因。\n",
    "* 尝试将Fashion-MNIST中图像的高和宽由224改为96。这在实验中有哪些影响？\n",
    "* 参考VGG论文里的表1来构造VGG其他常用模型，如VGG-16和VGG-19 [1]。\n",
    "\n",
    "\n",
    "\n",
    "## 参考文献\n",
    "\n",
    "[1] Simonyan, K., & Zisserman, A. (2014). Very deep convolutional networks for large-scale image recognition. arXiv preprint arXiv:1409.1556.\n",
    "\n",
    "## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/1277)\n",
    "\n",
    "![](../img/qr_vgg.svg)"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}